/**
 * serializeObject
 * */
(function($){
    if(!$.fn.serializeObject){
        $.fn.serializeObject = function(){
            var o = {};
            var a = this.serializeArray();
            $.each(a, function () {
                if (o[this.name] !== undefined) {
                    if (!o[this.name].push) {
                        o[this.name] = [o[this.name]];
                    }
                    o[this.name].push(this.value || '');
                } else {
                    o[this.name] = this.value || '';
                }
            });
            return o;
        };
    }
})(jQuery);
/**
 *
 * jq.gantt-1.0.1
 * @author Brant Liu
 * @email lbf1988@qq.com
 * @date 2020/5/28
 * @version 1.0.1
 */
(function (global, factory) {
    "use strict";

    factory(global);
})(typeof window !== "undefined" ? window : this, function (window) {
    "use strict";

    var document = window.document,
        $win = $(window),
        $doc = $(document),
        initDate = new Date();

    var ELEM_SCROLLBAR = 'gantt-layout-x-scrollbar',
        ELEM_SCROLL_X_WRAPPER = ".gantt-layout-x-scroll", ELEM_SCROLL_Y_WRAPPER = ".gantt-layout-y-scroll", ELEM_SCROLL_WRAPPER = '.gantt-scroll-wrapper',
        ELEM_CONTENT = '.gantt-layout-content',
        ELEM_GRID = '.gantt-layout-grid-left', ELEM_CALENDAR = '.gantt-layout-grid-right',
        ELEM_HEADER = '.gantt-layout-header', ELEM_ROW = '.gantt-grid-row', ELEM_CELL = '.gantt-grid-cell',
        ELEM_DATA = '.gantt-layout-data',
        ELEM_MODAL = '.gantt-modal', ELEM_ADD = '.gantt-add', ELEM_MODAL_WRAPPER = '.gantt-modal-wrapper',
        PROPERTY_TEXT = 'text', PROPERTY_START_DATE = 'startDate', PROPERTY_DURATION = 'duration';

    var SCROLL_BAR_ELEM = '<div class="gantt-scroll-wrapper"><div class="gantt-layout-content"></div></div>';

    var config = {
        index: 20200527000,
        debug: true,
        properties: {
            number: '_gantt_number',
            parent: 'parent',
            id: 'id',
            text: PROPERTY_TEXT,
            startDate: PROPERTY_START_DATE,
            endDate: 'endDate',
            duration: PROPERTY_DURATION,
            finalStartDate: 'finalStartDate',
            finalEndDate: 'finalEndDate',
            finalDuration: 'finalDuration',
            progress: 'progress',
            owner: 'owner'
        },
        scaleColumn: {
            year: {unit: 'year', step: 1, format: 'yyyy'},
            quarter: {
                unit: 'quarter', step: 1,
                format: function (date) {
                    return 'Q' + Gantt.dateQuarter(date);
                }
            },
            month: {unit: 'month', step: 1, format: 'MM'},
            weekNum: {
                unit: 'weekNum', step: 1,
                format: function (date) {
                    return Gantt.weekNo(date);
                }
            },
            week: {
                unit: 'week', step: 1,
                format: function (date) {
                    return Gantt.week(date);
                }
            },
            day: {unit: 'day', step: 1, format: 'dd'}
        },
        scales: ['week', 'day', 'weekNum', 'month', 'quarter', 'year'],
        minUnit: 'day',
        //甘特图默认列
        columns: {
            text: {field: PROPERTY_TEXT, text: 'Task Name', width: '*'},
            startDate: {field: PROPERTY_START_DATE, text: 'Start Date', width: 100},
            duration: {field: PROPERTY_DURATION, text: 'Duration', width: 80}
        },
        instance: []
    };

    var Gantt = function (selector, options) {
        this.index = ++config.index;
        this.options = $.extend({}, this.options, options);
        this.$parent = $(selector);
        this.init();
    };

    Gantt.fn = Gantt.prototype;

    /**
     * 前置补零
     * */
    Gantt.digit = function (value, length) {
        var str = '';
        value = String(value), length = length || 2;
        for (var i = value.length; i < length; i++) {
            str += '0';
        }
        return value < Math.pow(10, length) ? str + (value | 0) : value;
    };

    /**
     * 日期格式化
     * */
    Gantt.date = function (value, format) {
        var _date = new Date(value || new Date()),
            ymd = [Gantt.digit(_date.getFullYear(), 4), Gantt.digit(_date.getMonth() + 1), Gantt.digit(_date.getDate())],
            hms = [Gantt.digit(_date.getHours()), Gantt.digit(_date.getMinutes()), Gantt.digit(_date.getSeconds())];
        format = format || 'yyyy-MM-dd HH:mm:ss';
        return format.replace(/yyyy/g, ymd[0]).replace(/MM/g, ymd[1]).replace(/dd/g, ymd[2]).replace(/HH/g, hms[0]).replace(/mm/g, hms[1]).replace(/ss/g, hms[2]);
    };

    Gantt.datePlus = function (value, number) {
        var date = new Date(value || new Date());
        date.setDate(date.getDate() + number);
        return date;
    };

    /**
     * 获取两个日期之间的天数差
     * */
    Gantt.dateBetween = function (start, end) {
        var date1 = new Date(start.getFullYear(), start.getMonth(), start.getDate()),
            date2 = new Date(end.getFullYear(), end.getMonth(), end.getDate());
        return (date2.getTime() - date1.getTime()) / (1 * 24 * 60 * 60 * 1000);
    };

    /**
     * 获取两个日期范围所有日期
     * */
    Gantt.dateRange = function (start, end) {
        return Gantt.next(Gantt.dateBetween(start, end), start);
    };

    /**
     * 获取季度
     * */
    Gantt.dateQuarter = function (date) {
        var month = date.getMonth() + 1;
        return Math.floor((month % 3 == 0 ? (month / 3) : (month / 3 + 1)));
    };

    /**
     * 计算数组相邻值重复的次数
     * */
    Gantt.valueAdjoinCount = function (array) {
        var arr = [];
        array = $.isArray(array) ? array : [array];
        $.each(array, function (i, val) {
            var lastObj = arr[arr.length - 1];
            if(!lastObj){
                arr[0] = {};
                arr[0][val] = 1;
                return;
            }
            if(lastObj[val]){
                lastObj[val]++;
            } else {
                var length = arr.length;
                arr[length] = {};
                arr[length][val] = 1;
            }
        });
        return arr;
    };

    /**
     * 获取星期
     * */
    Gantt.week = function (date) {
        var arr = ["日", "一", "二", "三", "四", "五", "六"];
        return arr[date ? date.getDay() : initDate.getDay()];
    };

    /**
     * 根据日期 - 获取年的第几周
     * */
    Gantt.weekNo = function (date) {
        var start = new Date(date.getFullYear(), 0, 1),
            //今年第多少天
            day = Gantt.dateBetween(start, date);
        //day + 年的第一天的周差距的和在除以7就是本年第几周
        return Math.ceil((day + ((start.getDay() + 1) - 1)) / 7) || 1;
    };

    /**
     * 获取以往多少天的日期组合
     * */
    Gantt.prev = function (dayNumber) {
        dayNumber = dayNumber || 1;
        var now = new Date(), arr = [now];
        for (var i = 1; i <= dayNumber; i++) {
            var day = new Date(now);
            day.setDate(day.getDate() - i);
            arr.push(day);
        }
        //升序日期返回
        return arr.reverse();
    };

    /**
     * 获取未来多少天的日期组合
     * */
    Gantt.next = function (dayNumber, start) {
        dayNumber = dayNumber || 1;
        var now = start || new Date(), arr = [now];
        for (var i = 1; i < dayNumber; i++) {
            var day = new Date(now);
            day.setDate(day.getDate() + i);
            arr.push(day);
        }
        return arr;
    };

    var thisInst = function(){
        var self = this,
            options = self.options;
        return {
            options: options,
            add: function(){
                self.add.call(self);
            },
            refresh: function(index, id){
                self.refresh.call(self, index, id);
            },
            update: function(start, end){
                self.run(function(){
                    self.update.call(self, start, end);
                });
            },
            schedule: function($row, start, end, final){
                self.renderSchedule.call(self, $row, start, end, final);
            }
        };
    };

    /**
     * 外部初始化接口
     * */
    Gantt.init = function (selector, options) {
        var inst = new Gantt(selector, options);
        return thisInst.call(inst);
    };

    /**
     * 头部
     * */
    var ganttHeaderTemplate = function(callback){
        var self = this
        var tpl = [
            '<div class="gantt-layout-content">',
                '<div class="gantt-layout-header">',
                    callback ? callback.call(self) : '',
                '</div>',
                '<div class="gantt-layout-data"></div>',
            '</div>',
            '<div class="gantt-layout-x-scroll"></div>'
        ];
        return tpl.join('');
    };

    /**
     * 甘特图模板
     * */
    var ganttTemplate = function () {
        var self = this, options = self.options, p = config.properties,
            supportedWidth = self.gridWidth,
            width = options.width ? options.width : self.getParentWidth();
        var tpl = [
            '<div class="gantt-layout" style="min-width:'+ (supportedWidth + supportedWidth/2) +'px; height:'+ options.height +'px;">',
                '<div class="gantt-layout-view">',
                    '<div class="gantt-layout-grid gantt-layout-grid-left" style="'+ (supportedWidth && 'width: ' + self.gridWidth + 'px;') +'">',
                        ganttHeaderTemplate.call(self, function(){
                            var columns = self.gridColumn[0], cell = ['<div class="gantt-grid-row">'];
                            if(columns){
                                $.each(columns, function(i, column){
                                    cell.push('<div class="gantt-grid-cell"><span>' + column[p.text] + '</span></div>');
                                });
                            }
                            cell.push('</div>');
                            return cell.join('');
                        }),
                    '</div>',
                    '<div class="gantt-layout-resizer">',
                        options.resize ? '<div class="gantt-layout-content"></div>' : '',
                    '</div>',
                    '<div class="gantt-layout-grid gantt-layout-grid-right" style="'+ (supportedWidth && 'width:' + (width - self.gridWidth) + 'px;') +'">',
                        ganttHeaderTemplate.call(self, function(){
                            var dates = Gantt.next(options.maxDateNumber || 30, options.date), cell = ['<div class="gantt-grid-row">'];
                            $.each(dates, function(i, date){
                                cell.push('<div class="gantt-grid-cell"><span>' + Gantt.date(date, 'dd') + '</span></div>');
                            });
                            cell.push('</div>');
                            return cell.join('');
                        }),
                    '</div>',
                    '<div class="gantt-layout-y-scroll"></div>',
                    (function(){
                        var style = self.gridColumnStyle,
                            tpl = ['<style type="text/css">'];
                        $.each(style, function(i, item){
                            tpl.push(item);
                        });
                        tpl.push('</style>');
                        return tpl.join('');
                    }()),
                '</div>',
                options.add ? '<div class="gantt-add"><span>+</span></div>' : '',
                '<div class="gantt-modal"></div>',
            '</div>'
        ];

        return tpl.join('');
    };

    /**
     * 弹窗模板
     * */
    var ganttModal = function(row){
        var self = this, options = self.options, p = config.properties;

        row = row || {};

        var name = row[p.text] || '',
            startDate = Gantt.date(row[p.startDate], options.format),
            duration = row[p.duration],
            finalStartDate = row[p.finalStartDate] ? Gantt.date(row[p.finalStartDate], options.format) : '',
            finalDuration = row[p.finalDuration] || '';

        var tpl = [
            '<div class="gantt-modal-wrapper">',
                '<div class="gantt-modal-title">任务信息</div>',
                '<div class="gantt-modal-form">',
                    '<form>',
                        '<div class="form-group">',
                            '<label for="ganttName">任务描述</label>',
                            '<input type="text" class="form-control form-control-sm" name="text" value="'+ name +'" placeholder="任务名称/描述" id="ganttName">',
                        '</div>',
                        '<div class="row">',
                            ' <div class="col-9">',
                                '<div class="form-group">',
                                    '<label for="ganttStartDate">计划时间</label>',
                                    '<input type="text" class="form-control form-control-sm" name="startDate" value="'+ startDate +'" placeholder="计划时间" id="ganttStartDate">',
                                '</div>',
                            '</div>',
                            '<div class="col-3">',
                                '<div class="form-group">',
                                    '<label for="ganttDuration">天数</label>',
                                    '<input type="number" class="form-control form-control-sm" name="duration" value="'+ duration +'" placeholder="计划天数" id="ganttDuration">',
                                '</div>',
                            '</div>',
                        '</div>',
                        '<div class="row">',
                            ' <div class="col-9">',
                                '<div class="form-group">',
                                    '<label for="ganttFinalStartDate">实际时间</label>',
                                    '<input type="text" class="form-control form-control-sm" name="finalStartDate" value="'+ finalStartDate +'" placeholder="实际时间" id="ganttFinalStartDate">',
                                '</div>',
                            '</div>',
                            '<div class="col-3">',
                                '<div class="form-group">',
                                    '<label for="ganttFinalDuration">天数</label>',
                                    '<input type="number" class="form-control form-control-sm" name="finalDuration" value="'+ finalDuration +'" placeholder="实际天数" id="ganttFinalDuration">',
                                '</div>',
                            '</div>',
                        '</div>',
                        '<div class="gantt-modal-footer">',
                            '<button type="button" class="btn btn-sm btn-primary gantt-btn-submit">保 存</button>',
                            '<button type="button" class="btn btn-sm btn-secondary gantt-btn-cancel">取 消</button>',
                        '</div>',
                    '</form>',
                '</div>',
            '</div>'
        ];

        return tpl.join('');
    };

    /**
     * columns [] to {}
     * */
    var colToObject = function (arr, key) {
        key = key || 'field';
        var obj = {};
        if (!$.isArray(arr)) {
            arr = [arr];
        }
        $.each(arr, function (i, item) {
            if (item.hasOwnProperty(key)) {
                obj[item.field] = item;
            }
        });
        return obj;
    };

    /**
     * columns {} to []
     * */
    var colToArray = function (obj) {
        var arr = [];
        for (var key in obj) {
            arr.push(obj[key]);
        }
        return arr;
    };

    /**
     * 日期冒泡排序
     * */
    var sort = function(arr, callback){
        for (var i = 0; i < arr.length - 1; i++) {
            for (var j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    var temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
        callback && callback.call(this, arr);
    };

    /**
     * 清除属性
     * */
    var remove = function(obj){
        var object = $.extend({}, obj);
        delete object[config.properties.number];
        return object;
    };

    /**
     * 配置
     * */
    Gantt.fn.options = {
        //甘特图默认宽度
        width: undefined,
        //甘特图默认高度
        height: 500,
        //列最小宽度
        minWidth: 100,
        //列名称默认宽度
        gridWidthNoneTaskFieldWidth: 200,
        //甘特图右侧日历单元格最小宽度
        calendarCellMinWidth: 31,
        //甘特图左侧表格默认序列号开关
        ganttNo: true,
        //甘特图左右容器拖动大小开关
        resize: true,
        /*
        * 甘特图左侧表格计算规则
        * > 1 直接体现为左侧宽度
        * = 0 根据列计算宽度
        * < 1 按照父容器百分比计算宽度
        * */
        gridWidth: 0,
        format: 'yyyy-MM-dd',
        //甘特图右侧日历计算默认开始日期
        date: initDate,
        //甘特图右侧日历计算默认最大日期
        maxDateNumber: 60,
        //添加按钮
        add: false,
        //右侧日历默认显示列
        scale: [
            {
                unit: 'weekNum', format: function (date) {
                    return '第'+ Gantt.weekNo(date) +'周';
                }
            },
            {unit: 'day', format: 'dd'},
            {
                unit: 'week', format: function(date){
                    return Gantt.week(date);
                }
            }
        ],
        change: function(data, index){}
    };

    /**
     * 初始化
     * */
    Gantt.fn.init = function () {
        var self = this, options = self.options,
            $parent = self.$parent;

        self.prepare().initGridColumns();
        self.initGridCellProperty();

        var $ganttTpl = self.$gantt = $(ganttTemplate.call(self));
        delete self.gridColumnStyle;

        $parent.append($ganttTpl);

        self.$ganttGrid = $parent.find(ELEM_GRID);
        self.$ganttGridHeader = self.$ganttGrid[0] && self.$ganttGrid.find(ELEM_HEADER);
        self.$ganttGridData = self.$ganttGrid[0] && self.$ganttGrid.find(ELEM_DATA);

        self.$ganttCalendar = $parent.find(ELEM_CALENDAR);
        self.$ganttCalendarHeader = self.$ganttCalendar[0] && self.$ganttCalendar.find(ELEM_HEADER);
        self.$ganttCalendarData = self.$ganttCalendar[0] && self.$ganttCalendar.find(ELEM_DATA);

        self.$ganttScrollbarY = $parent.find(ELEM_SCROLL_Y_WRAPPER);

        self.$ganttModal = $parent.find(ELEM_MODAL);

        self.initCalendarScale();
        //默认渲染
        self.update(options.date, Gantt.datePlus(options.date, options.maxDateNumber));

        self.run(function(){
            self.renderData();
        });

        config.instance.push(self);

        self.events();

        self.startTime && console.log('消耗时间：', (new Date().getTime() - self.startTime) + 'ms');
    };

    /**
     * 初始化准备
     * */
    Gantt.fn.prepare = function () {
        var self = this, options = self.options, date = options.date || new Date(),
            $parent = self.$parent;

        options.id = options.id || self.$parent.attr('id') || ('gantt-' + self.index);

        $parent.attr('id') || $parent.attr('id', options.id);
        $parent && $parent[0] && $parent.empty();

        options.date = new Date(date.getFullYear(), date.getMonth(), date.getDate());

        return self;
    };

    /**
     * 甘特图初始化左侧列属性配置
     * */
    Gantt.fn.initGridColumns = function () {
        var self = this, options = self.options, columns = options.columns || [];

        var columnConfig = colToArray($.extend(true, config.columns, colToObject(columns)));

        /**
         * 重新按照外部列定义
         * */
        var columnFinal = columns.length > 0 ? [] : columnConfig;
        $.each(columns, function (i, item) {
            $.each(columnConfig, function (i2, item2) {
                if (item2.field == item.field) {
                    columnFinal.push(item2);
                }
            });
        });

        if (options.ganttNo) {
            //TODO 添加序列号列
            columnFinal.unshift({field: config.properties.number, text: '#', width: 40});
        }

        $.each(columnFinal, function (i, col) {
            if (!col.width) {
                col.width = options.minWidth;
            }
            col.resize = col.resize || false;
        });
        self.gridColumn = [columnFinal];

        return self;
    };

    /**
     * 甘特图初始化左侧表格
     * */
    Gantt.fn.initGridCellProperty = function () {
        var self = this, options = self.options,
            scale = options.gridWidth,
            isPercent = scale < 1 && scale > 0,
            parentWidth = self.getParentWidth(),
            stylePrefix = ['#', options.id, ' ', '.gantt-layout-grid-left', ' ', '.gantt-grid-cell'].join(''),
            style = [];

        var sumWidth = self.getGridColumnSum();
        var width = isPercent ? parseInt(parentWidth * scale) : (scale ? (sumWidth > scale ? scale : sumWidth) : sumWidth);
        self.gridWidth = width;

        var columnUseWidth = 0;
        self.each(function (i, item) {
            if (!isNaN(item.width)) {
                columnUseWidth += item.width;
            }
        });
        var autoWidth = width - columnUseWidth;
        self.each(function (i, item) {
            var width = item.cellWidth ? item.cellWidth : item.width;
            if (isPercent && width == '*') {
                width = autoWidth;
            }
            style.push(stylePrefix + ':nth-child(' + (i + 1) + '){width: ' + width + 'px}');
        });
        self.gridColumnStyle = style;

        return self;
    };

    /**
     * 初始化甘特图列
     * */
    Gantt.fn.initCalendarScale = function(){
        var self = this, options = self.options,
            scales = options.scale,
            scaleColumn = config.scaleColumn,
            scaleSortRule = config.scales,
            newScale = [],
            finalScale = [];

        //日历列定义检查
        scales ? $.each(scales, function (i, scale) {
            var type = $.type(scale);
            switch (type) {
                case 'object':
                    var option = scale.unit ? scaleColumn[scale.unit] : scale;
                    newScale.push($.extend({}, option, scale));
                    break;
                case 'string':
                    var val = scaleColumn[scale];
                    val && newScale.push(val);
                    break;
            }
        }) : (newScale = scales);

        //日历列排序
        $.each(scaleSortRule, function (i, rule) {
            $.each(newScale, function (i, scale) {
                if(rule == scale.unit){
                    finalScale.push(scale);
                }
            });
        });
        self.scale = finalScale;
        //self.scaleMin = finalScale[0];

        return self;
    };

    /**
     * 初始化甘特图右侧日历
     * 日历表头定义必须由大到小
     * */
    Gantt.fn.initCalendar = function(start, end){
        var self = this, options = self.options, scales = self.scale,
            gridWidth = self.gridWidth,
            width = options.width ? options.width : self.getParentWidth();
        /**
         * 日期
         * 年    year        2020
         * 季    quarter     q1, q2, q3, q4
         * 月    month       1,2,3,4,5,6.....
         * 周    weekNum     第1周，第2周.....
         * 日    day         1,2,3.....
         * 星期  week        星期一，星期二，星期三，星期四，星期五..
         * */
        //最小单元时间范围
        var range = Gantt.dateRange(start, end);
        //单元格数量
        self.calendarCellNumber = range.length;

        //单元没有铺满的情况，增加日期
        var fullWidth = self.calendarCellNumber * options.calendarCellMinWidth;
        if (fullWidth < (width - gridWidth)) {
            var gapCell = Math.ceil((width - gridWidth) / options.calendarCellMinWidth) - self.calendarCellNumber;
            if (gapCell) {
                var min = range[self.calendarCellNumber - 1];
                for (var i = 0; i < gapCell; i++) {
                    range.push(Gantt.datePlus(min, i + 1));
                }
                self.calendarCellNumber = range.length;
            }
        }
        //TODO 计算最后三天，第几周不相等的情况需要补全日期，不然第几周显示会出问题。

        $.each(scales, function(i, scale){
            var arr = [], type = $.type(scale.format);
            $.each(range, function (i, date) {
                switch (type) {
                    case 'function':
                        var result = scale.format(date);
                        arr.push(result ? result : config.scaleColumn.quarter.format(date));
                        break;
                    case 'string':
                        arr.push(Gantt.date(date, scale.format));
                        break;
                }
            });
            console.log(arr);
            scale['value'] = Gantt.valueAdjoinCount(arr);
        });
        return self;
    };

    /**
     * 初始化甘特图右侧表头
     * */
    Gantt.fn.renderCalendar = function () {
        var self = this, scales = self.scale,
            $header = self.$ganttCalendarHeader;

        $header.empty();
        $.each(scales, function (i, scale) {
            var row = ['<div class="gantt-grid-row">'], dataCell = [];
            $.each(scale.value, function (i, item) {
                var key = Object.getOwnPropertyNames(item)[0],
                    count = item[key], style = [];
                if (count > 1) {
                    style.push('style="width:');
                    self.scaleMinCellWidth && style.push(self.scaleMinCellWidth * count);
                    style.push('px;"');
                }
                row.push('<div class="gantt-grid-cell" '+ (style.join('')) +'><span>' + key + '</span></div>');
                dataCell.push('<div class="gantt-grid-cell"></div>');
            });
            row.push('</div>');
            $header.prepend(row.join(''));
            if(0 === i){
                self.scaleMinCellWidth = $header.find(ELEM_CELL).outerWidth() || self.options.calendarCellMinWidth;
                self.scaleCellHtml = dataCell.join('');
                delete self.scaleCellHtml;
            }
        });

        return self;
    };

    /**
     * 刷新列定义
     * */
    Gantt.fn.update = function (start, end) {
        var self = this;

        self.startTime = new Date().getTime();

        self.initCalendar(start, end)
            .renderCalendar()
            .initScrollBar()
            .updateGridHeader();

        return self;
    };

    /**
     * resize 后更新相关属性
     * */
    Gantt.fn.updateProperty = function(){
        var self = this, options = self.options,
            gridWidth = self.gridWidth,
            width = options.width ? options.width : self.getParentWidth();

        //更新calendar容器宽度
        if(self.$ganttCalendar && self.$ganttCalendar[0]){
            self.$ganttCalendar.width(width - gridWidth - self.getScrollYWidth());
        }

        return self;
    };

    /**
     * 数据渲染
     * */
    Gantt.fn.renderData = function () {
        var self = this, options = self.options, data = options.data;

        var type = $.type(data);

        if('string' === type){

        } else if('array' === type){
            self.renderBeforeHandle(data).render(data);
        }
    };

    /**
     * 渲染前处理
     * */
    Gantt.fn.renderBeforeHandle = function (data) {
        var self = this,
            p = config.properties;

        var repo = self.dateRepo = [];

        $.each(data, function(index, item){
            var start = item[p.startDate],
                end = item[p.endDate],
                finalStart = item[p.finalStartDate],
                finalEnd = item[p.finalEndDate];

            item[p.startDate] = start = start ? ($.type(start) == 'date' ? start : new Date(start)) : new Date();
            item[p.endDate] = end = end ? ($.type(end) == 'date' ? end : new Date(end)) : start;

            item[p.duration] = item[p.duration] || (Gantt.dateBetween(start, end) + 1);

            finalStart && (item[p.finalStartDate] = finalStart = finalStart ? ($.type(finalStart) == 'date' ? finalStart : new Date(finalStart)) : undefined);
            finalStart && (item[p.finalEndDate] = finalEnd = finalEnd ? ($.type(finalEnd) == 'date' ? finalEnd : new Date(finalEnd)) : finalStart);
            finalStart && (item[p.finalDuration] = item[p.finalDuration] || (Gantt.dateBetween(finalStart, finalEnd) + 1));

            start && (repo.push(start));
            end && (repo.push(end));
            finalStart && (repo.push(finalStart));
            finalEnd && (repo.push(finalEnd));
        });

        sort.call(self, repo, function(arr){
            self.dateMin = arr[0];
            self.dateMax = arr[arr.length - 1];

            self.update(self.dateMin, Gantt.datePlus(self.dateMax, 1));
        });
        delete self.dateRepo;

        return self;
    };

    /**
     * 渲染数据
     * */
    Gantt.fn.render = function(data, append){
        var self = this,
            p = config.properties,
            $grid = self.$ganttGridData,
            $calendar = self.$ganttCalendarData,
            calendarRow = ['<div class="gantt-grid-row">'],
            options = self.options;

        //追加 不清除内容
        if(!append){
            $grid.empty();
            $calendar.empty();
        }

        for (var i = 0, len = self.calendarCellNumber; i < len; i++) {
            calendarRow.push('<div class="gantt-grid-cell"></div>');
        }
        calendarRow.push('</div>');

        $.each(data, function(index, row){
            var $row = $('<div class="gantt-grid-row"></div>'),
                $calendarRow = $(calendarRow.join('')),
                start = row[p.startDate],
                end = row[p.endDate],
                finalStart = row[p.finalStartDate],
                finalEnd = row[p.finalEndDate];

            self.run(function(){
                //计划进度
                self.renderSchedule($calendarRow, start, end);
                //实际进度
                (finalStart && finalEnd) && self.renderSchedule($calendarRow, finalStart, finalEnd, true);
            });

            self.each(function(i, item){
                var $cell = $('<div class="gantt-grid-cell"></div>');
                if(item.field == p.number){
                    $cell.text(row[p.number] ? row[p.number] : (index + 1));
                } else {
                    var value = self.getCellValue(row, item);
                    $cell.html(value);
                }
                $row.append($cell);
            });

            $grid.append($row);
            $calendar.append($calendarRow);
        });

        self.initScrollBar();

        return self;
    };

    /**
     * 甘特图进度
     * @param {jQueryDOM} $row 行
     * @param {Date} start 开始时间
     * @param {Date} end 结束时间
     * @param {Boolean} final 是否实际处理
     * */
    Gantt.fn.renderSchedule = function ($row, start, end, final) {
        var self = this, dateMin = self.dateMin,
            width = self.scaleMinCellWidth,
            index = Gantt.dateBetween(dateMin, start),
            number = Gantt.dateBetween(start, end) + 1,
            widthGap = 4;
        if ($row && $row[0]) {
            var scheduleWidth = width * number - widthGap,
                left = width * index + widthGap / 2;

            var $schedule = $row.find((!final) ? '.gantt-schedule' : '.gantt-schedule-final');

            if(!$schedule[0]){
                $row.append($schedule = $('<div class="'+ ((!final) ? 'gantt-schedule' : 'gantt-schedule-final') +'"></div>'));
            }

            $schedule.css({
                width: scheduleWidth + 'px',
                left: left + 'px'
            });
        }
    };

    Gantt.fn.getCellValue = function (row, item) {
        var self = this,
            field = item.field,
            value = row[field] || '',
            vFormat = $.type(item.format),
            vType = $.type(value);
        if ('date' === vType) {
            value = Gantt.date(value, vFormat === 'string' ? item.format : self.options.format);
        }
        if (item.format && 'function' === vFormat) {
            value = item.format.call(this, value, item) || '';
        }
        return value;
    }

    Gantt.fn.run = function (callback, timer) {
        var self = this;
        timer = timer || 20;
        setTimeout(function () {
            callback.call(self);
        }, timer);
        return self;
    };

    /**
     * 初始化滚动条
     * */
    Gantt.fn.initScrollBar = function () {
        var self = this,
            $header = self.$ganttGridHeader,
            $headerCalendar = self.$ganttCalendarHeader;

        var scrollXGridSupported = function(){
            return self.getContentWidth() > $header.outerWidth();
        };

        var scrollXCalendarSupported = function(){
            return self.getContentWidth(self.$ganttCalendar) > $headerCalendar.outerWidth();
        };

        var initGridScrollX = function () {
            //supported = scrollXGridSupported(),
            var $wrapper = self.$ganttGrid.find(ELEM_SCROLL_WRAPPER),
                $content = $wrapper.find(ELEM_CONTENT);

            ($wrapper && $wrapper[0]) && $wrapper.width($header.outerWidth());
            ($content && $content[0]) && $content.width(self.getContentWidth());
        };

        var initCalendarScrollX = function () {
            //supported = scrollXCalendarSupported();
            var $wrapper = self.$ganttCalendar.find(ELEM_SCROLL_WRAPPER),
                $content = $wrapper.find(ELEM_CONTENT);

            ($wrapper && $wrapper[0]) && $wrapper.width($headerCalendar.outerWidth());
            ($content && $content[0]) && $content.width(self.getContentWidth(self.$ganttCalendar));
        };

        var initScrollY = function(){
            var height = self.getContentHeight(),
                conHeight = self.getContentHeight(true),
                $scroll = self.$ganttScrollbarY.find(ELEM_SCROLL_WRAPPER),
                isInit = ($scroll && $scroll[0]) ? true : false;
            if(scrollXGridSupported() || scrollXCalendarSupported()){
                height = height - self.$parent.find(ELEM_SCROLL_X_WRAPPER).height();
            }
            if (conHeight > height) {
                var $scrollWrapper = isInit ? $scroll.parent(ELEM_SCROLL_Y_WRAPPER) : $(SCROLL_BAR_ELEM);
                $scrollWrapper.css('marginTop', self.getHeaderHeight());
                $scrollWrapper.css('height', height);
                $scrollWrapper.find(ELEM_CONTENT).height(conHeight);

                self.$ganttGridData.height(height);
                self.$ganttCalendarData.height(height);

                isInit || self.$ganttScrollbarY.append($scrollWrapper);
            }
        };

        if(scrollXGridSupported() || scrollXCalendarSupported()){
            self.$ganttGrid.find(ELEM_SCROLL_X_WRAPPER).html(SCROLL_BAR_ELEM);
            self.$ganttCalendar.find(ELEM_SCROLL_X_WRAPPER).html(SCROLL_BAR_ELEM);
        }

        initGridScrollX();
        initCalendarScrollX();

        initScrollY();

        self.bindScrollEvent();

        return self;
    };

    /**
     * 滚动条事件初始化
     * */
    Gantt.fn.bindScrollEvent = function () {
        var self = this, $parent = self.$gantt,
            $scrollXGrid = self.$ganttGrid.find(ELEM_SCROLL_X_WRAPPER + ' ' + ELEM_SCROLL_WRAPPER),
            $scrollXCalendar = self.$ganttCalendar.find(ELEM_SCROLL_X_WRAPPER + ' ' + ELEM_SCROLL_WRAPPER),
            $scrollY = $parent.find(ELEM_SCROLL_Y_WRAPPER + ' ' + ELEM_SCROLL_WRAPPER);

        $scrollY.off('scroll').on('scroll', function(){
            var othis = $(this), scrollbarTop = othis.scrollTop();

            self.$ganttGridData.scrollTop(scrollbarTop);
            self.$ganttCalendarData.scrollTop(scrollbarTop);
        });

        $scrollXGrid.off('scroll').on('scroll', function(){
            var othis = $(this), scrollLeft = othis.scrollLeft();

            self.$ganttGridHeader.scrollLeft(scrollLeft);
            self.$ganttGridData.scrollLeft(scrollLeft);
        });

        $scrollXCalendar.off('scroll').on('scroll', function(){
            var othis = $(this), scrollLeft = othis.scrollLeft();

            self.$ganttCalendarHeader.scrollLeft(scrollLeft);
            self.$ganttCalendarData.scrollLeft(scrollLeft);
        });

        self.bindMouseWheelEvent();
    };

    /**
     * 鼠标滚轮事件
     * */
    Gantt.fn.bindMouseWheelEvent = function(){
        var self = this,
            $parent = self.$gantt,
            $scrollData = $parent.find(ELEM_DATA),
            $scrollY = $parent.find(ELEM_SCROLL_Y_WRAPPER + ' ' + ELEM_SCROLL_WRAPPER);

        function getDeltaFromEvent(e) {
            var deltaX = e.originalEvent.deltaX;
            var deltaY = -1 * e.originalEvent.deltaY;

            if (typeof deltaX === "undefined" || typeof deltaY === "undefined") {
                // OS X Safari
                deltaX = -1 * e.originalEvent.wheelDeltaX / 6;
                deltaY = e.originalEvent.wheelDeltaY / 6;
            }

            if (e.originalEvent.deltaMode && e.originalEvent.deltaMode === 1) {
                // Firefox in deltaMode 1: Line scrolling
                deltaX *= 10;
                deltaY *= 10;
            }

            if (deltaX !== deltaX && deltaY !== deltaY/* NaN checks */) {
                // IE in some mouse drivers
                deltaX = 0;
                deltaY = e.originalEvent.wheelDelta;
            }

            return [deltaX, deltaY];
        }

        function mousewheelHandler(e) {
            var delta = getDeltaFromEvent(e);

            var deltaX = delta[0];
            var deltaY = delta[1];

            $scrollY.scrollTop($scrollY.scrollTop() - (deltaY * 1));
        }

        if (typeof window.onwheel !== "undefined") {
            $scrollData.bind('wheel', mousewheelHandler);
        } else if (typeof window.onmousewheel !== "undefined") {
            $scrollData.bind('mousewheel', mousewheelHandler);
        }
    };

    Gantt.fn.addModal = function(index, row, callback){
        var self = this;

        var $tpl = $(ganttModal.call(self, row));
        self.$ganttModal.html($tpl);
        self.$ganttModal.children(ELEM_MODAL_WRAPPER).attr('data-index', index).data('row', row);
        self.$ganttModal.addClass('show');

        var $wrapper = self.$ganttModal.find(ELEM_MODAL_WRAPPER);
        $wrapper.on('change', 'input', function(event){
            self.modalChange = true;
            $wrapper.removeAttr('data-add');
        });

        callback && callback.call(self);
    };

    Gantt.fn.removeModal = function () {
        var self = this,
            data = self.options.data,
            $grid = self.$ganttGridData,
            $calendar = self.$ganttCalendarData,
            $wrapper = self.$ganttModal.children(ELEM_MODAL_WRAPPER);
        if ($wrapper.data('add')) {
            var index = data.length - 1;
            data.splice(index, 1);
            $grid.find(ELEM_ROW).eq(index).remove();
            $calendar.find(ELEM_ROW).eq(index).remove();
        }
        self.$ganttModal.html('');
        self.$ganttModal.removeClass('show');
        delete self.modalChange;
    };

    Gantt.fn.submitModal = function(){
        var self = this, p = config.properties,
            $wrapper = self.$ganttModal.children(ELEM_MODAL_WRAPPER),
            index = $wrapper.data('index'),
            row = $wrapper.data('row'),
            $row = self.$ganttGridData.find(ELEM_ROW).eq(index),
            $calendarRow = self.$ganttCalendarData.find(ELEM_ROW).eq(index);
        if (!self.modalChange) {
            self.removeModal();
            return;
        }
        var newRow = $wrapper.find('form').serializeObject(),
            refreshSchedule = false;
        self.options.data[index][p.text] = newRow[p.text];
        if(newRow[p.startDate]){
            newRow[p.startDate] = new Date(newRow[p.startDate]);
            refreshSchedule = true;
        } else {
            delete newRow[p.startDate];
        }
        if(newRow[p.duration]){
            newRow[p.duration] = parseInt(newRow[p.duration]);
            refreshSchedule = true;
        } else {
            delete newRow[p.duration];
        }
        if(newRow[p.finalStartDate]){
            newRow[p.finalStartDate] = new Date(newRow[p.finalStartDate]);
            refreshSchedule = true;
        } else {
            delete newRow[p.finalStartDate];
        }
        if(newRow[p.finalDuration]){
            newRow[p.finalDuration] = parseInt(newRow[p.finalDuration]);
            refreshSchedule = true;
        } else {
            delete newRow[p.finalDuration];
        }
        //需要更新时间调度
        if (refreshSchedule) {
            var start = newRow[p.startDate],
                duration = newRow[p.duration],
                end = newRow[p.endDate] = Gantt.datePlus(start, duration - 1),
                //实际
                finalStart = newRow[p.finalStartDate],
                finalDuration = finalStart ? (newRow[p.finalDuration] || 1) : null,
                finalEnd = newRow[p.finalEndDate] = finalStart ? Gantt.datePlus(finalStart, finalDuration - 1) : null,
                repo = [], isUpdate = false;

            //计划
            if (start) {
                repo.push(start);
                repo.push(end);
                self.options.data[index][p.startDate] = start;
                self.options.data[index][p.duration] = duration;
                self.options.data[index][p.endDate] = end;
            }
            //实际
            if(finalStart){
                repo.push(finalStart);
                repo.push(finalEnd);
                self.options.data[index][p.finalStartDate] = finalStart;
                self.options.data[index][p.finalDuration] = finalDuration;
                self.options.data[index][p.finalEndDate] = finalEnd;
            }

            sort.call(self, repo, function (arr) {
                var min = arr[0];
                var max = arr[arr.length - 1];

                var diffMinDay = Gantt.dateBetween(self.dateMin, min),
                    diffMaxDay = Gantt.dateBetween(self.dateMax, max);

                if (diffMinDay < 0 || diffMaxDay > 0) {
                    if (diffMinDay < 0) {
                        self.dateMin = min;
                    }
                    if (diffMaxDay > 0) {
                        self.dateMax = max;
                    }
                    self.update(self.dateMin, Gantt.datePlus(self.dateMax, 1)).render(self.options.data);
                    isUpdate = true;
                }
            });

            if (!isUpdate) {
                //计划
                start && self.renderSchedule($calendarRow, start, end);
                //实际
                finalStart && self.renderSchedule($calendarRow, finalStart, finalEnd, true);
            }
        }
        self.each(function(i, item){
            if(newRow[item.field]){
                var value = self.getCellValue(newRow, item);
                $row.find(ELEM_CELL).eq(i).html(value);
            }
        });
        self.options.change(remove(self.options.data[index]), index);
        self.removeModal();
    };

    Gantt.fn.add = function(){
        var self = this, p = config.properties, options = self.options,
            data = options.data,
            index = data.length;

        var obj = {};
        obj[p.number] = index + 1;
        obj[p.text] = '新任务';
        obj[p.startDate] = obj[p.endDate] = new Date();
        obj[p.duration] = 1;

        data[index] = obj;

        self.render([obj], true);
        self.addModal(index, obj, function(){
            self.$ganttModal.children(ELEM_MODAL_WRAPPER).attr('data-add', 'true');
        });
        return self;
    };

    /**
     * 更新数据
     * @param {Number} 行
     * @param {Number} 数据Id编号
     * */
    Gantt.fn.refresh = function (index, obj) {
        var self = this,
            p = config.properties,
            options = self.options,
            data = options.data;
        if(arguments.length != 2){
            return;
        }
        data[index][p.id] = obj;
    };

    /**
     * 事件
     * */
    Gantt.fn.events = function () {
        var self = this, $parent = self.$gantt, $modal = self.$ganttModal;

        //行编辑弹窗
        $parent.on('dblclick', '.gantt-layout-grid-left .gantt-layout-data .gantt-grid-row', function (event) {
            var $this = $(this), index = $this.index(), data = self.options.data, row = data[index];
            self.addModal(index, row);
        });

        $parent.on('click', ELEM_ADD, function(event){
            event.preventDefault();
            self.add();
        });

        $modal.on('click', '.gantt-btn-submit', function (event) {
            event.preventDefault();
            self.submitModal();
        });

        $modal.on('click', '.gantt-btn-cancel', function (event) {
            event.preventDefault();
            self.removeModal();
        });
    };

    /**
     * window resize
     * */
    Gantt.fn.resize = function(){
        var self = this;
        self.updateProperty();
    };

    /**
     * 获取已知列总宽度
     * */
    Gantt.fn.getGridColumnSum = function () {
        var self = this, options = self.options,
            //列名称默认宽度
            minWidth = options.gridWidthNoneTaskFieldWidth,
            width = 0;

        self.each(function (i, item) {
            if (!item.width || item.width == '*') {
                width += item.cellWidth = minWidth;
            } else {
                width += item.width;
            }
        });

        return width || 500;
    };

    /**
     * 获取初始化甘特图容器的宽度
     * */
    Gantt.fn.getParentWidth = function ($resizer) {
        //减去中间的宽度及中间的分割线
        return this.$parent.outerWidth() - 2 - ($resizer && $resizer.outerWidth() ? $resizer.outerWidth() : 1);
    };

    /**
     * 获取垂直滚动条宽度
     * */
    Gantt.fn.getScrollYWidth = function () {
        var self = this, $parent = self.$ganttScrollbarY;
        return $parent.outerWidth() || 0;
    };

    Gantt.fn.getContentHeight = function(flag){
        var self = this,
            $data = self.$ganttGridData,
            height = self.options.height;
        if(flag){
            var h = 0;
            $data.find(ELEM_ROW).each(function(){
                h += $(this).outerHeight();
            });
            return h;
        }
        return height - (self.getHeaderHeight() || 0);
    };

    Gantt.fn.getHeaderHeight = function(){
        var self = this, $header = self.$ganttGridHeader;
        return $header.outerHeight();
    };

    /**
     * 更新水平滚动条
     * */
    Gantt.fn.getContentWidth = function ($parent) {
        $parent = $parent ? $parent : this.$ganttGrid;
        return $parent.find(ELEM_HEADER).prop('scrollWidth');
    };

    /**
     * 更新甘特图左侧表头
     * */
    Gantt.fn.updateGridHeader = function () {
        var self = this, scale = self.scale,
            $header = self.$ganttGridHeader,
            cellHeight = self.$ganttCalendarHeader.find(ELEM_ROW).eq(0).outerHeight();
        if (scale && $header && $header[0]) {
            $header.css({
                'height': ((scale.length * cellHeight) + 'px'),
                'line-height': ((scale.length * cellHeight - 12) + 'px')
            });
        }
        return self;
    };

    /**
     * 遍历甘特图表格
     * */
    Gantt.fn.each = function (callback) {
        var self = this, columns = self.gridColumn[0];
        $.each(columns, function (i, item) {
            callback && callback.apply(self, [i, item]);
        });
    };

    /**
     * 监听窗口resize变化
     * */
    $win.on('resize', function(){
        var instances = config.instance, len = instances.length;
        if(instances && len > 0){
            for (var i = 0; i < len; i++) {
                var ins = instances[i];
                ins && ins.resize.call(ins);
            }
        }
    });

    window.Gantt = Gantt;

    return Gantt;
});